﻿using Microsoft.Extensions.ObjectPool;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Canteen.Core.Common
{
    public class RedisCacheManager : IRedisCacheManager
    {
        private readonly ObjectPool<RedisCache> _redisPool;
        public RedisCacheManager(ObjectPool<RedisCache> redisPool)
        {
            _redisPool = redisPool;
        }

        public TEntity RedisCache<TEntity>(Func<IDatabase, TEntity> func, int dbNum)
        {
            var redis = _redisPool.Get();
            if (redis == null) throw new InvalidOperationException("ObjectPool Redis Is Null");
            try
            {
                return func(redis.GetDatabase(dbNum));
            }
            finally
            {
                _redisPool.Return(redis);
            }
        }

        public TEntity RedisCache<TEntity>(Func<IDatabase, TEntity> func)
        {
            var redis = _redisPool.Get();
            if (redis == null) throw new InvalidOperationException("ObjectPool Redis Is Null");
            try
            {
                return func(redis.GetDatabase());
            }
            finally
            {
                _redisPool.Return(redis);
            }
        }

        public async Task<string> GetAsync(string key, int dbNum)
        {
            if (dbNum > 0) return await RedisCache(db => db.StringGetAsync(key), dbNum);
            return await RedisCache(db => db.StringGetAsync(key));
        }

        public async Task<TEntity> GetAsync<TEntity>(string key, int dbNum)
        {
            if (dbNum > 0) return ConvertObj<TEntity>(await RedisCache(db => db.StringGetAsync(key), dbNum));
            return ConvertObj<TEntity>(await RedisCache(db => db.StringGetAsync(key)));
        }

        public async Task<bool> SetAsync(string key, string value, TimeSpan expiry, int dbNum)
        {
            if (dbNum > 0) return await RedisCache(db => db.StringSetAsync(key, value, expiry), dbNum);
            return await RedisCache(db => db.StringSetAsync(key, value, expiry));
        }
        public async Task<bool> SetAsync<TEntity>(string key, TEntity value, TimeSpan expiry, int dbNum)
        {
            if (dbNum > 0) return await RedisCache(db =>
                   db.StringSetAsync(key, ConvertJson(value), expiry),
                   dbNum
               );
            return await RedisCache(db => db.StringSetAsync(key, ConvertJson(value), expiry));
        }


        public async Task<bool> ExistsAsync(string key, int dbNum)
        {
            if (dbNum > 0) return await RedisCache(db => db.KeyExistsAsync(key), dbNum);
            return await RedisCache(db => db.KeyExistsAsync(key));
        }
        public async Task<bool> DeleteAsync(string key, int dbNum)
        {
            if (dbNum > 0) return await RedisCache(db => db.KeyDeleteAsync(key), dbNum);
            return await RedisCache(db => db.KeyDeleteAsync(key));
        }
        public async Task ClearAll()
        {
            var redis = _redisPool.Get();
            if (redis == null) throw new InvalidOperationException("ObjectPool Redis Is Null");
            try
            {
                foreach (var endPoint in redis.Connection.GetEndPoints())
                {
                    var server = redis.Connection.GetServer(endPoint);
                    await foreach (var key in server.KeysAsync())
                    {
                        await redis.Connection.GetDatabase().KeyDeleteAsync(key);
                    }
                }
            }
            finally
            {
                _redisPool.Return(redis);
            }
        }

        public string ConvertJson<TEntity>(TEntity value)
        {
            try
            {
                return value is string ? value.ToString() : JsonConvert.SerializeObject(value);
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
        }
        public TEntity ConvertObj<TEntity>(RedisValue value)
        {
            try
            {
                if (value.IsNullOrEmpty) return default;
                else return JsonConvert.DeserializeObject<TEntity>(value);
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
        }
        public List<TEntity> ConvetList<TEntity>(RedisValue[] values)
        {
            if (values.Length > 0)
            {
                List<TEntity> result = new List<TEntity>();
                foreach (var item in values)
                {
                    var model = ConvertObj<TEntity>(item);
                    result.Add(model);
                }
                return result;
            }
            else return default;
        }
        public RedisKey[] ConvertRedisKeys(List<string> redisKeys)
        {
            return redisKeys.Select(redisKey => (RedisKey)redisKey).ToArray();
        }

    }
}
